#ifndef OBJECTS_H
#define OBJECTS_H

#include "GeometricObjects.h"
#include "Point3D.h"
#include "Normal.h"
#include "Ray.h"
#include "ShadeRec.h"
#include "RGBColor.h"
#include "Common.h"
#include <math.h>
#include "BoundingBox.h"
#include "BVH.h"
#include "Triangle.h"
#include "ObjLoader.h"
#include <iostream>

using std::cout;

class Plane : public GeometricObjects
{
private:
	//point on the plane 
	Point3D point;
	//normal to the plane
	Normal normal;

public:
	using GeometricObjects::hit;
	virtual bool hit(const Point3D& o, const Point3D& d, double &tmin, ShadeRec &sr) const 
	{
		double distance = DOT(normal, d);
		if (distance != 0)
		{
			double dist = (((DOT(normal, o) + 4.4))/distance) * -1;

			if (dist < tmin)
			{
				tmin = dist;
				return true;
			}
		}

		return false;
	}

	using GeometricObjects::MakeBoundingBox;
	void MakeBoundingBox()
	{
		//return (new Engine::BoundingBox());
	}

	using GeometricObjects::GetNormal;
	Point3D GetNormal(Point3D& p_hitPoint)
	{
		return Point3D(normal.x, normal.y, normal.z);
	}

	using GeometricObjects::GetBoundingBox;
	Engine::BoundingBox* GetBoundingBox()
	{
		return new Engine::BoundingBox();
	}

	Plane(void)
	{
		normal = Normal();
		point = Point3D(0);
	}

	Plane(const Point3D &_point, const Normal &_normal)
	{
		point = _point;
		normal = _normal;
	}

	Plane(const Point3D& _point, const Point3D& _normal, RGBColor _color)
	{
		point = _point;
		normal = Normal(_normal);
		color = _color;
	}
	void SetColor(RGBColor& _color)
	{
		color = _color;
	}

	void SetColor(const float _r, const float _g, const float _b)
	{
		color = RGBColor(_r, _g, _b);
	}

	RGBColor GetColor()
	{
		return color;
	}


	void SetPoint(const Point3D& _point)
	{
		point = _point;
	}

	void SetNormal(const Point3D& normalPoint)
	{
		normal = Normal(normalPoint);
	}

	void SetNormal(const float _x, const float _y, const float _z)
	{
		normal = Normal(_x, _y, _z);
	}

	virtual void PrintObjectName()
	{
		cout << "Sphere at location " + point.toString() << "---------" << std::endl;
	}
};


class Sphere : public GeometricObjects
{
public:

	Point3D centerPoint;
	float radius;

	Sphere()
	{}

	Sphere(Point3D p_center, float p_radius, Engine::Material* p_mtrPtr) : centerPoint(p_center), radius(p_radius)
	{
		SetMaterial(p_mtrPtr);
		MakeBoundingBox();
	}

	/*Sphere(Point3D m_center, float m_radius, Engine::Material* p_mtrPtr) : centerPoint(m_center), radius(m_radius)
	{
		SetMaterial(p_mtrPtr);
		MakeBoundingBox();

	}*/

	Sphere(Point3D m_center, float m_radius, RGBColor& m_color) : centerPoint(m_center), radius(m_radius)
	{
		color = m_color;
		MakeBoundingBox();

	}

	void SetColor(RGBColor& _color)
	{
		color = _color;
	}

	void SetColor(const float _r, const float _g, const float _b)
	{
		color = RGBColor(_r, _g, _b);
	}

	RGBColor GetColor()
	{
		return color;
	}


	void SetRadius(float _radius)
	{
		radius = _radius;
	}

	void SetCenterPoint(float p_x, float p_y, float p_z)
	{
		centerPoint = Point3D(p_x, p_y, p_z);
	}

	void SetCenterPoint(const Point3D& point)
	{
		centerPoint = point;
	}

	using GeometricObjects::GetNormal;
	Point3D GetNormal(Point3D p_hitPoint)
	{
		return radius * (p_hitPoint - centerPoint);
	}

	using GeometricObjects::hit;
	virtual bool hit(Point3D o, Point3D d, double &tmin, ShadeRec& sr)
	{
		Point3D l = centerPoint - o;
		float tca = l.dot(d);
		if (tca < 0) return false;
		float d2 = l.dot(l) - tca * tca;
		if (d2 > radius) return false;
		float thc = sqrt(radius - d2);
		float t0= tca - thc;
		float t1 = tca + thc;
		if (t0 > kEpsilon && t1 > kEpsilon && (t0 < tmin || t1 < tmin))
		{

			if (t0 < t1 && t0 > kEpsilon)
			{
				tmin = t0;

			}
			else
			{
				tmin = t1;
			}

			sr.localHitPoint = o + (d * tmin);
			sr.normal = (((double)radius)* (sr.localHitPoint - centerPoint));
			sr.normal.normalize();
			sr.object = ((GeometricObjects*)this);
			sr.hitAnObject = true;
			sr.myColor = GetColor();
			sr.tmin = tmin;
			return (true);
		}

		return false;
	}
		

	using GeometricObjects::MakeBoundingBox;
	void MakeBoundingBox()
	{
		Point3D minPoint(centerPoint.x - radius, centerPoint.y - radius, centerPoint.z - radius);
		Point3D maxPoint(centerPoint.x + radius, centerPoint.y + radius, centerPoint.z + radius);

		box = new Engine::BoundingBox(minPoint, maxPoint);
	}

	virtual void PrintObjectName()
	{
		cout << "Sphere at location " + centerPoint.toString() << "---------" << std::endl;
	}
};


class Tris  :public GeometricObjects
{
public:
	Engine::BVH *triangleSoup;

	Tris(std::string p_path, Engine::Material* p_material, Point3D centroid = Point3D(0), bool enhance = false)
	{
		vector<GeometricObjects*> soup = centroid != 0 ? ObjLoader().LoadFile(p_path, centroid) : ObjLoader().LoadFile(p_path);
		triangleSoup = new Engine::BVH(soup,Engine::PartitioningStyle::MIDDLE_POINT, enhance);
		SetMaterial(p_material);
		MakeBoundingBox();
	}



	using GeometricObjects::hit;
	virtual bool hit(Point3D o, Point3D d, double &tmin, ShadeRec &sr)
	{
		Ray currentRay = Ray(o, d);
		bool intersection = triangleSoup->TestIntersection(currentRay, sr, tmin);
		if (intersection)
		{
			sr.object = this;
		}

		return intersection;
	}

	using GeometricObjects::MakeBoundingBox;
	void MakeBoundingBox()
	{
		box = triangleSoup->rootNode->boundingBox;
	}

	Engine::BoundingBox* GetBoundingBox()
	{
		return box;
	}

	using GeometricObjects::GetNormal;
	Point3D GetNormal(Point3D p_hitPoint)
	{
		return Point3D();
	}

	virtual void PrintObjectName()
	{
		cout << "Triangle soup " + GetMaterial()->GetMaterialName() << "---------" << endl;
	}

	~Tris()
	{
		delete triangleSoup;
	}
private:
};


#endif // !OBJECTS_H
